{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 背景建模"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 帧差法\n",
    "\n",
    "由于场景中的目标在运动，目标的影像在不同图像帧中的位置不同。该类算法对时间上连续的两帧图像进行差分运算，不同帧对应的像素点相减，判断灰度差的绝对值，当绝对值超过一定阈值时，即可判断为运动目标，从而实现目标的检测功能。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![title](bg_2.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "帧差法非常简单，但是会引入噪音和空洞问题"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 混合高斯模型\n",
    "\n",
    "在进行前景检测前，先对背景进行训练，对图像中每个背景采用一个混合高斯模型进行模拟，每个背景的混合高斯的个数可以自适应。然后在测试阶段，对新来的像素进行GMM匹配，如果该像素值能够匹配其中一个高斯，则认为是背景，否则认为是前景。由于整个过程GMM模型在不断更新学习中，所以对动态背景有一定的鲁棒性。最后通过对一个有树枝摇摆的动态背景进行前景检测，取得了较好的效果。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在视频中对于像素点的变化情况应当是符合高斯分布"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![title](bg_3.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "背景的实际分布应当是多个高斯分布混合在一起，每个高斯模型也可以带有权重"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![title](bg_4.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 混合高斯模型学习方法\n",
    "\n",
    "- 1.首先初始化每个高斯模型矩阵参数。\n",
    "\n",
    "- 2.取视频中T帧数据图像用来训练高斯混合模型。来了第一个像素之后用它来当做第一个高斯分布。\n",
    "\n",
    "- 3.当后面来的像素值时，与前面已有的高斯的均值比较，如果该像素点的值与其模型均值差在3倍的方差内，则属于该分布，并对其进行参数更新。\n",
    "\n",
    "- 4.如果下一次来的像素不满足当前高斯分布，用它来创建一个新的高斯分布。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 混合高斯模型测试方法\n",
    "\n",
    "在测试阶段，对新来像素点的值与混合高斯模型中的每一个均值进行比较，如果其差值在2倍的方差之间的话，则认为是背景，否则认为是前景。将前景赋值为255，背景赋值为0。这样就形成了一副前景二值图。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![title](bg_5.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "not enough values to unpack (expected 3, got 2)",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-1-73272c18f4bc>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m     15\u001b[0m     \u001b[0mfgmask\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mcv2\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mmorphologyEx\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mfgmask\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcv2\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mMORPH_OPEN\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mkernel\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     16\u001b[0m     \u001b[1;31m#寻找视频中的轮廓\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 17\u001b[1;33m     \u001b[0mim\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcontours\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mhierarchy\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mcv2\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfindContours\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mfgmask\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcv2\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mRETR_EXTERNAL\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcv2\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mCHAIN_APPROX_SIMPLE\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     18\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     19\u001b[0m     \u001b[1;32mfor\u001b[0m \u001b[0mc\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mcontours\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mValueError\u001b[0m: not enough values to unpack (expected 3, got 2)"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import cv2\n",
    "\n",
    "#经典的测试视频\n",
    "cap = cv2.VideoCapture('test.avi')\n",
    "#形态学操作需要使用\n",
    "kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))\n",
    "#创建混合高斯模型用于背景建模\n",
    "fgbg = cv2.createBackgroundSubtractorMOG2()\n",
    "\n",
    "while(True):\n",
    "    ret, frame = cap.read()\n",
    "    fgmask = fgbg.apply(frame)\n",
    "    #形态学开运算去噪点\n",
    "    fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)\n",
    "    #寻找视频中的轮廓\n",
    "    im, contours, hierarchy = cv2.findContours(fgmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n",
    "\n",
    "    for c in contours:\n",
    "        #计算各轮廓的周长\n",
    "        perimeter = cv2.arcLength(c,True)\n",
    "        if perimeter > 188:\n",
    "            #找到一个直矩形（不会旋转）\n",
    "            x,y,w,h = cv2.boundingRect(c)\n",
    "            #画出这个矩形\n",
    "            cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),2)    \n",
    "\n",
    "    cv2.imshow('frame',frame)\n",
    "    cv2.imshow('fgmask', fgmask)\n",
    "    k = cv2.waitKey(150) & 0xff\n",
    "    if k == 27:\n",
    "        break\n",
    "\n",
    "cap.release()\n",
    "cv2.destroyAllWindows()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
